<?php
/**
 * @Author: gan
 * @Description:
 * @File:  Xlswriter
 * @Version: 1.0.0
 * @Date: 2021/12/28 8:46 上午
 */

declare(strict_types=1);

namespace RX\Component\XlsWriter;

use RX\Enums\CommonEnum;
use RX\Enums\XlsWriterEnum;
use RX\Exception\BizException;
use RX\Helpers\FileHelper;
use Vtiful\Kernel\Excel;
use Vtiful\Kernel\Format;

class XlsWriter
{
    public const PATH = DIRECTORY_SEPARATOR . 'Static' . DIRECTORY_SEPARATOR;

    public const TYPE_STRING    = Excel::TYPE_STRING;
    public const TYPE_INT       = Excel::TYPE_INT;
    public const TYPE_DOUBLE    = Excel::TYPE_DOUBLE;
    public const TYPE_TIMESTAMP = Excel::TYPE_TIMESTAMP;

    /**
     * 导入
     * @param string $filePath 文件路径
     * @param string $sheet sheet
     * @param array $setType 列的类型
     * @return array
     * @throws BizException
     */
    public static function import(string $filePath, string $sheet = "Sheet1", $setType = [])
    {
        try {
            $filename = basename($filePath);
            $dirPath  = dirname($filePath);
            $config   = ['path' => $dirPath];
            $excel    = new Excel($config);
            if (empty($setType)) {
                $setType = array_fill(0, 10, Excel::TYPE_STRING);
            }
            return $excel->openFile($filename)
                         ->openSheet($sheet, Excel::SKIP_EMPTY_ROW)
                         ->openSheet($sheet, Excel::SKIP_EMPTY_CELLS)
                         ->setType($setType)
                         ->setSkipRows(0)
                         ->getSheetData();
        } catch (\Throwable $e) {
            throw new BizException(CommonEnum::class, CommonEnum::SERVER_NOT_FOUND);
        }
    }


    /**
     * @param array $headerData 头部数据
     * @param array $contentData 内容数据
     * @param string $dirPath 存储目录
     * @return array|string
     * @throws BizException
     */
    public static function normalExport(array $headerData, array $contentData, string $dirPath = 'Default')
    {
        self::normalHandle($headerData, $contentData, $content, $dirPath, $saveName);
        $excelMod = (new Excel(['path' => $dirPath]))->fileName($saveName, 'Sheet1');
        self::commonStyle($excelMod, $headerData);
        return $excelMod->header(array_values($headerData))->data($content)->output();
    }


    /**
     * @param array $headerData 头部数据
     * @param array $contentData 内容数据
     * @param array $mergeData 合并数据
     * @param array $styleData 样式
     * @return string
     * @throws BizException
     */
    public static function diyExport(array $headerData, array $contentData, array $mergeData, array $styleData = [])
    {
        self::normalHandle($headerData, $contentData, $content, $dirPath, $saveName);
        $excelMod = (new Excel(['path' => $dirPath]))->fileName($saveName, 'Sheet1');
        $excelMod->header(array_values($headerData))->data($content);
        $fileHandle = $excelMod->getHandle();

        foreach ($styleData['column'] ?? [] as $value) {
            if (!isset($value['range'])) {
                throw new BizException(XlsWriterEnum::class, XlsWriterEnum::COLUMN_ERROR);
            }
            $formatColSub = new Format($fileHandle);
            self::formatStyleDiy($value, $formatColSub);
            $excelMod->setColumn($value['range'], $value['width'] ?? 15, $formatColSub->toResource());
        }

        foreach ($styleData['row'] ?? [] as $value) {
            if (!isset($value['range'])) {
                throw new BizException(XlsWriterEnum::class, XlsWriterEnum::ROW_ERROR);
            }
            $formatRowSub = new Format($fileHandle);
            self::formatStyleDiy($value, $formatRowSub);
            $excelMod->setRow($value['range'], $value['height'] ?? 20, $formatRowSub->toResource());
        }

        // 合并数据
        foreach ($mergeData as $value) {
            if (!isset($value['range'])) {
                throw new BizException(XlsWriterEnum::class, XlsWriterEnum::RANGE_ERROR);
            }
            $formatMergeSub = new Format($fileHandle);
            self::formatStyleDiy($value, $formatMergeSub);
            $excelMod->mergeCells($value['range'], $value['data'] ?? '', $formatMergeSub->toResource());
        }
        return $excelMod->output();
    }

    /**
     * 通用处理
     * @param array $headerData 头部数据
     * @param array $contentData 内容数据
     * @param array $content 返回的数据
     * @param string $dirPath 返回的路径
     * @param string $saveName 返回的文件名
     * @throws BizException
     */
    private static function normalHandle(array $headerData, array $contentData, &$content, &$dirPath, &$saveName)
    {
        $contentKeys = array_keys($headerData);
        $content     = [];
        foreach ($contentData as $value) {
            $contentSub = [];
            foreach ($contentKeys as $v) {
                $contentSub[] = $value[$v] ?? '';
            }
            $content[] = $contentSub;
        }
        $dirPath  = self::getSavePath('Default', false);
        $saveName = self::getFileSaveName();
    }


    /**
     * @param string $dir 目录
     * @param bool $isTmp 是否临时
     * @return bool|string
     * @throws BizException
     */
    public static function getSavePath(string $dir, bool $isTmp = true): string
    {
        if ($isTmp) {
            $tmp = ini_get('upload_tmp_dir');
            if ($tmp !== false && file_exists($tmp)) {
                return realpath($tmp);
            }
            return realpath(sys_get_temp_dir());
        }

        $savePath = EASYSWOOLE_ROOT . static::PATH . $dir . DIRECTORY_SEPARATOR . date('Ymd') . DIRECTORY_SEPARATOR;
        if (!FileHelper::createDirectory($savePath)) {
            throw new BizException(XlsWriterEnum::class, XlsWriterEnum::CREATE_DIR_ERROR);
        }
        return $savePath;
    }


    /**
     * 文件名称 (必须唯一) 规则: sha1(文件名+微秒+百万随机)
     * @param string $ext 后缀
     * @return string
     */
    public static function getFileSaveName(string $ext = 'xlsx'): string
    {
        return sha1(uniqid() . microtime(true) . mt_rand(100000, 999999)) . "." . $ext;
    }


    /**
     * 格式化
     * @param array $style 样式数据
     * @param Format $format 格式对象
     */
    private static function formatStyleDiy(array $style, Format &$format)
    {
        if (isset($style['font']['size'])) {
            $format->fontSize($style['font']['size']);
        }
        if (isset($style['font']['font'])) {
            $format->font($style['font']['font']);
        }
        if (isset($style['font']['bold'])) {
            $format->bold();
        }
        if (isset($style['font']['wrap'])) {
            $format->wrap();
        }

        switch ($style['align'] ?? null) {
            case 'center_center':
                $format->align(Format::FORMAT_ALIGN_CENTER, Format::FORMAT_ALIGN_VERTICAL_CENTER);
                break;
            case 'left_center':
                $format->align(Format::FORMAT_ALIGN_LEFT, Format::FORMAT_ALIGN_VERTICAL_CENTER);
                break;
            case 'right_center':
                $format->align(Format::FORMAT_ALIGN_RIGHT, Format::FORMAT_ALIGN_VERTICAL_CENTER);
                break;
            default:
                break;
        }
        switch ($style['border']) {
            case 'thin':
                $format->border(Format::BORDER_THIN);
                break;
            case 'dotted':
                $format->border(Format::BORDER_DOTTED);
                break;
                break;
            default:
                break;
        }
    }


    /**
     * 设置列样式
     * @param string $range
     * @param int $width
     * @param array $font
     * @param string $align
     * @param string $border
     * @return array
     */
    public static function setColumnStyle(
        string $range,
        int $width = 15,
        string $border = null,
        string $align = 'center_center',
        array $font = []
    ): array {
        return [
            'range'  => $range,
            'width'  => $width ?? 15,
            'font'   => [
                'size' => $font['size'] ?? 10,
                'font' => $font['font'] ?? 'Calibri',
                'bold' => $font['bold'] ?? null,
                'wrap' => $font['wrap'] ?? null,
            ],
            'align'  => $align ?? null,
            'border' => $border,
        ];
    }

    /**
     * 设置行样式
     * @param string $range
     * @param int $height
     * @param array $font
     * @param string $align
     * @param null $border
     * @return array
     */
    public static function setRowStyle(
        string $range,
        int $height = 15,
        $border = null,
        $align = 'center_center',
        $font = []
    ): array {
        return [
            'range'  => $range,
            'height' => $height ?? 15,
            'font'   => [
                'size' => $font['size'] ?? 10,
                'font' => $font['font'] ?? 'Calibri',
                'bold' => $font['bold'] ?? null,
                'wrap' => $font['wrap'] ?? null,
            ],
            'align'  => $align ?? null,
            'border' => $border,
        ];
    }

    /**
     * 设置合并样式
     * @param string $range
     * @param string $data
     * @param array $font
     * @param string $align
     * @param string $border
     * @return array
     */
    public static function setMergeStyle(
        string $range,
        string $data,
        string $border = null,
        string $align = 'center_center',
        array $font = []
    ): array {
        return [
            'range'  => $range,
            'data'   => $data,
            'font'   => [
                'size' => $font['size'] ?? 10,
                'font' => $font['font'] ?? 'Calibri',
                'bold' => $font['bold'] ?? null,
                'wrap' => $font['wrap'] ?? null,
            ],
            'align'  => $align ?? null,
            'border' => $border,
        ];
    }


    /**
     * 数字转字母 （Excel列标）
     * @param int $index 索引值
     * @param int $start 字母起始值
     * @return String 返回字母
     */
    public static function intToChr(int $index, int $start = 65): string
    {
        $str = '';
        if (floor($index / 26) > 0) {
            $str .= self::IntToChr(intval(floor($index / 26)) - 1);
        }
        return $str . chr($index % 26 + $start);
    }


    /**
     * 分片导出
     * @param int $chunk 分片数量
     * @param callable $func 回调
     * @param bool $isTmp 是否临时文件
     * @param string $path 文件路径
     * @return mixed
     * @throws BizException
     */
    public static function chunkExport(int $chunk, callable $func, bool $isTmp = true, string $path = 'Default')
    {
        $dirPath  = self::getSavePath($path, $isTmp);
        $saveName = self::getFileSaveName();
        $excelObj = (new Excel(['path' => $dirPath]))->fileName($saveName);

        // 创建sheet
        for ($i = 2; $i <= $chunk; $i++) {
            $excelObj->addSheet("Sheet{$i}");
        }
        $filePath = $excelObj->data([])->output();
        return call_user_func($func, $excelObj, $filePath);
    }


    /**
     * 设置Sheet数据
     * @param Excel $excelObj 文件对象
     * @param string $sheetName sheet名称
     * @param array $header header
     * @param array $data 数据
     * @return string
     */
    public static function setSheetDataOutput(Excel $excelObj, string $sheetName, array $header, array $data)
    {
        $excelObj->checkoutSheet($sheetName);
        self::commonStyle($excelObj, $header);
        $result = [];
        self::commonData($header, $data, $result);
        return $excelObj->header(array_values($header))->data($result)->output();
    }


    /**
     * 公共样式
     * @param Excel $excelObj 文件对象
     * @param array $header header
     */
    private static function commonStyle(&$excelObj, $header)
    {
        $fileHandle    = $excelObj->getHandle();
        $formatHeader  = new Format($fileHandle);
        $formatContent = new Format($fileHandle);

        // title style
        $titleStyle = $formatHeader->fontSize(12)
                                   ->bold()
                                   ->font("Calibri")
                                   ->align(Format::FORMAT_ALIGN_CENTER, Format::FORMAT_ALIGN_VERTICAL_CENTER)
                                   ->toResource();

        // global style
        $globalStyle = $formatContent->fontSize(10)
                                     ->font("Calibri")
                                     ->align(Format::FORMAT_ALIGN_CENTER, Format::FORMAT_ALIGN_VERTICAL_CENTER)
                                     ->border(Format::BORDER_THIN)
                                     ->toResource();

        $headerLen = count(array_values($header));
        $columnEnd = strtoupper(chr(65 + $headerLen - 1));
        // column style
        $excelObj->setColumn("A:{$columnEnd}", 15, $globalStyle);
        // title
        $excelObj->setRow("A1", 20, $titleStyle);
        // 冻结表头
        $excelObj->freezePanes(1, 0);
    }


    /**
     * 公共数据
     * @param array $header header
     * @param array $data 数据
     * @param array $result 返回数据
     */
    private static function commonData(array $header, array $data, array &$result)
    {
        $keys = array_keys($header);
        array_map(function ($value) use (&$result, $keys) {
            $valueKyes = array_keys($value);
            $diff      = array_values(array_diff($valueKyes, $keys));
            foreach ($diff as $v) {
                unset($value[$v]);
            }
            array_push($result, array_values($value));
        }, $data);
    }


    /**
     * 多文件合并csv
     * @param string $dirPath sheet名称
     * @param string $targetFile 目标文件
     * @param array $files 文件碎片路径
     * @return array
     */
    public static function putCSV(string $dirPath, string $targetFile, array $files)
    {
        $excel = (new Excel(['path' => $dirPath]));
        $fp    = fopen($targetFile, 'a');

        $rt = [];
        foreach ($files as $val) {
            $rt[$val] = $excel->openFile(pathinfo($val, PATHINFO_BASENAME))
                              ->openSheet()
                              ->putCSV($fp);
        }
        return $rt;
    }
}
